package com.fzk.router;

import com.fzk.exception.IllegalParamsException;
import com.fzk.log.Logger;
import com.fzk.server.HttpResponseUtil;
import com.fzk.server.MyHttpUtil;
import com.fzk.utils.MyFileUtil;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.ReferenceCountUtil;
import org.apache.commons.lang3.RandomStringUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Pattern;


/**
 * 路由, 实现了自动注册路由
 *
 * @author fzk
 * @datetime 2023-02-13 21:26:15
 */
@ChannelHandler.Sharable // 此路由处理器是共享的, 不要有成员变量
public class RouterHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    // 拦截器
    private final ArrayList<InterceptorMapping> interceptors = new ArrayList<>();
    // 请求路径绝对匹配
    private final Map<String, ArrayList<HandlerMapping>> apiHandlerMap = new HashMap<>();
    // 请求路径前缀匹配
//    private final Map<String, ArrayList<HandlerMapping>> prefixHandlerMap = new HashMap<>();

    /**
     * @param fieldAutowireSourceMap 属性自动注入来源
     */
    public RouterHandler(Map<String, Object> fieldAutowireSourceMap) {
        // 自动探测并注册controller
        try {
            // 1.扫描controller包下所有标注有@AutoRegister的Java类作为组件类
            List<Class<?>> classes = MyFileUtil.loadPackageClasses("com.fzk.controller");
            for (Class<?> clazz : classes) {
                if (clazz.isAnnotationPresent(AutoRegister.class)) {
                    if (clazz.getDeclaredConstructors().length > 1) {
                        throw new RuntimeException(String.format("Class: %s 自动注册路由, 必须只能有1个无参构造器", clazz));
                    }
                    // 2.用无参构造器创建实例
                    Constructor<?> constructor = clazz.getConstructor();
                    Object instance = constructor.newInstance();

                    // 3.解析组件类的各个属性字段，准备路由注册和依赖注入
                    for (Field field : clazz.getDeclaredFields()) {
                        // 3.1.路由自动注册
                        if (field.isAnnotationPresent(MyRequestMapping.class)) {
                            field.setAccessible(true);// 设置属性可见, 避免private
                            Object fieldInstance = field.get(instance);// 获取属性实例
                            // 解析MyRequestMapping注解获取path和method
                            MyRequestMapping annotation = field.getAnnotation(MyRequestMapping.class);
                            String[] paths = annotation.path();
                            if (paths == null || paths.length == 0) {
                                throw new RuntimeException("非法path: " + Arrays.toString(paths));
                            }
                            String[] methods = annotation.method();
                            if (methods == null || methods.length == 0) {
                                throw new RuntimeException("非法method: " + Arrays.toString(paths));
                            }
                            if (fieldInstance instanceof ApiHandler) {
                                // 3.1.1 注册处理器
                                registerRouter(paths, methods, (ApiHandler) fieldInstance);
                            } else if (fieldInstance instanceof Interceptor) {
                                // 3.1.2 注册拦截器
                                registerInterceptor(paths, methods, (Interceptor) fieldInstance);
                            } else {
                                throw new RuntimeException(String.format("Class: %s 的属性: %s 存在MyRequestMapping注解, 却不是ApiHandler接口实现类", clazz, field));
                            }
                        } else if (field.isAnnotationPresent(MyAutowire.class)) {
                            // 3.2.属性自动依赖注入
                            MyAutowire autowire = field.getAnnotation(MyAutowire.class);
                            Object v = fieldAutowireSourceMap.get(autowire.key());
                            if (v != null) {
                                field.setAccessible(true);// 设置属性可见, 避免private
                                field.set(instance, v);
                            } else {
                                throw new RuntimeException(String.format("Class: %s 的属性: %s 存在MyAutowire注解, 却找不到自动注入类, key: %s", clazz, field, autowire.key()));
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void dispatch(MyCtx ctx, FullHttpRequest request) {
        // 1.解析请求路径
        // 此时的uri很可能是长这样的/hello?arg=1，需要解析出参数并获取简短uri
        QueryStringDecoder decoder = new QueryStringDecoder(request.uri(), StandardCharsets.UTF_8);
        String path = decoder.path();// 请求路径/hello
//        ctx.setLogAttr(MyCtx.PathKey, path);// 将请求路径放入ctx
        // 2.查找拦截器
        ArrayList<Interceptor> toIntercept = new ArrayList<>();
        for (InterceptorMapping interceptorMapping : interceptors) {
            if (interceptorMapping.match(path, request.method())) {
                toIntercept.add(interceptorMapping.interceptor);
            }
        }

        // 3.查找处理器
        HandlerMapping executor = null;
        // 3.1 优先-查找处理器-绝对路径匹配
        ArrayList<HandlerMapping> handlerMappings = apiHandlerMap.get(path);
        if (handlerMappings != null) {
            for (HandlerMapping handlerMapping : handlerMappings) {
                if (handlerMapping.match(path, request.method())) {
                    executor = handlerMapping;
                    break;
                }
            }
        }
        if (executor == null) {// 再遍历看看有没有模糊路径匹配的
            for (ArrayList<HandlerMapping> handlers : apiHandlerMap.values()) {
                for (HandlerMapping handler : handlers) {
                    if (handler.match(path, request.method())) {
                        executor = handler;
                        break;
                    }
                }
            }
        }

        // 没有处理器直接404
        if (executor == null) {
            HttpResponseUtil.sendError(ctx.getChannelCtx(), HttpUtil.isKeepAlive(request), HttpResponseStatus.NOT_FOUND);
        } else {
            // 4.参数解析: 这里仅解析form表单参数，json格式的参数需要另外手动解析
            Map<String, List<String>> params = new HashMap<>(decoder.parameters()); // 请求行参数
            if (MyHttpUtil.isContentType(request, HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED)) {
                try {// 请求体参数
                    Map<String, List<String>> formParams = MyHttpUtil.parseFormParams(request);
                    params.putAll(formParams);
                } catch (IllegalParamsException e) {
                    HttpResponseUtil.sendError(ctx.getChannelCtx(), HttpUtil.isKeepAlive(request), HttpResponseStatus.BAD_REQUEST);
                }
            }
            // 4.1 拦截器执行
            for (Interceptor interceptor : toIntercept) {
                if (!interceptor.intercept(ctx, request, params)) {
                    return;
                }
            }
            // 4.2 处理器执行
            executor.execute(ctx, request, params);
        }
    }

    public void registerInterceptor(String[] paths, String[] methods, Interceptor interceptor) {
        List<HttpMethod> methodList = new ArrayList<>(methods.length);
        for (String method : methods) {
            methodList.add(getHttpMethod(method));
        }
        // 拦截器注册不用校验是否重复添加相同uri和HTTP方法
//        List<String> prefixList = new ArrayList<>(paths.length);
//        for (String prefix : paths) {
//            if (prefix.endsWith("*")) {
//                prefix = prefix.substring(0, prefix.lastIndexOf("*"));
//            }
//            prefixList.add(prefix);
//        }
        interceptors.add(new InterceptorMapping(List.of(paths), methodList, interceptor));
        Logger.info(String.format("注册拦截器: path: %s, method: %s", Arrays.toString(paths), methodList));
    }

    public HttpMethod getHttpMethod(String method) {
        HttpMethod m;
        method = method.toUpperCase();
        switch (method) {
            case "GET" -> m = HttpMethod.GET;
            case "POST" -> m = HttpMethod.POST;
            case "PUT" -> m = HttpMethod.PUT;
            case "DELETE" -> m = HttpMethod.DELETE;
            case "OPTIONS" -> m = HttpMethod.OPTIONS;
            case "HEAD" -> m = HttpMethod.HEAD;
            case "PATCH" -> m = HttpMethod.PATCH;
            case "TRACE" -> m = HttpMethod.TRACE;
            case "CONNECT" -> m = HttpMethod.CONNECT;
            default -> throw new RuntimeException("不支持注册该HTTP方法: " + method);
        }
        return m;
    }

    public void registerRouter(String[] paths, String[] methods, ApiHandler apiHandler) {
        List<HttpMethod> methodList = Arrays.stream(methods).map(this::getHttpMethod).toList();
        for (String path : paths) {
            if (path == null || !path.startsWith("/")) {
                throw new RuntimeException("非法path: " + path);
            }
            if (apiHandlerMap.containsKey(path)) {
                List<HandlerMapping> handlerMappings = apiHandlerMap.get(path);
                // 2.1 不能注册相同path和HTTP方法
                for (HandlerMapping handlerMapping : handlerMappings) {
                    methodList.forEach(m -> {
                        if (handlerMapping.match(path, m)) {
                            throw new RuntimeException(String.format("重复注册路由: path: %s, method: %s", path, m));
                        }
                    });
                }
                handlerMappings.add(new HandlerMapping(path, methodList, apiHandler));
            } else {
                apiHandlerMap.put(path, new ArrayList<>(List.of(new HandlerMapping(path, methodList, apiHandler))));
            }
            Logger.info(String.format("注册路由: method: %s, path: %s", methodList, path));
        }
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
        MyCtx myctx = new MyDefaultCtx(String.format("%d%s", System.currentTimeMillis(), RandomStringUtils.secure().nextNumeric(5)), ctx);
        if (!request.decoderResult().isSuccess()) {
            HttpResponseUtil.sendError(ctx, HttpUtil.isKeepAlive(request), HttpResponseStatus.BAD_REQUEST);
            return;
        }
        // 这里做路由转发, 解码uri后放回去
        String uri = URLDecoder.decode(request.uri(), StandardCharsets.UTF_8);
        request.setUri(uri);// 把解码处理后的uri设置到request方便后续使用
        if (sanitizeUri(uri) == null) {// uri校验
            HttpResponseUtil.sendError(ctx, HttpUtil.isKeepAlive(request), HttpResponseStatus.BAD_REQUEST, "uri has some error: " + request.uri());
            return;
        }
        // 记录访问的远程地址和nginx转发原始地址
        myctx.info(String.format("uri: %s, method: %s, remoteAddr: %s, X-Real-IP: %s, X-Forwarded-For: %s, clientAddr(calculate): %s, cookie: %s",
                uri, request.method(), ctx.channel().remoteAddress(),
                request.headers().get(MyHttpUtil.HttpHeaderNameXRealIP), request.headers().get(MyHttpUtil.HttpHeaderNameXForwardedFor),
                MyHttpUtil.getClientAddress(ctx, request), request.headers().get(HttpHeaderNames.COOKIE)));

        // upload
        if (uri.startsWith("/fs/upload")) {
            // 继续向pipeline后传递，需要将ByteBuf的持有引用数+1
            // 否则SimpleChannelInboundHandler#channelRead的自动释放资源 ReferenceCountUtil.removeCallback(request)将报错
            ReferenceCountUtil.retain(request);
            ctx.fireChannelRead(request);// 将上传内容向后传递
            return;
        }

        // websocket 不处理，走到这是协议升级请求，不要做任何响应，ping早已pong过了
        if (uri.startsWith("/fs/websocket")) {
            return;
        }

        // 请求分发
        dispatch(myctx, request);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.channel().flush();// 兜底刷入，避免忘记手动刷
    }

    private static final Pattern INSECURE_URI = Pattern.compile(".*[<>\"].*");

    // uri合法检查
    private static String sanitizeUri(String uri) {
        // Decode the path.
        uri = URLDecoder.decode(uri, StandardCharsets.UTF_8);

        if (uri.isEmpty() || uri.charAt(0) != '/') {
            return null;
        }

        // Convert file separators.
        // path = path.replace('/', File.separatorChar);

        // Simplistic dumb security check.
        // You will have to do something serious in the production environment.
        if (uri.contains("/.") ||
                uri.contains("./") ||
                uri.charAt(0) == '.' || uri.charAt(uri.length() - 1) == '.' ||
                INSECURE_URI.matcher(uri).matches()) {
            return null;
        }
        return uri;
    }

    @Override// 错误拦截
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        Logger.error(cause.getMessage());
        if (ctx.channel().isActive()) {
            HttpResponseUtil.sendError(ctx, true, HttpResponseStatus.INTERNAL_SERVER_ERROR);
        }
    }
}

